[id].vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <template>
  2. <div class="admin--manager-form">
  3. <div v-if="isLoading" class="admin--loading">
  4. 데이터를 불러오는 중...
  5. </div>
  6. <form v-else @submit.prevent="handleSubmit" class="admin--form">
  7. <!-- 지점명 -->
  8. <div class="admin--form-group">
  9. <label class="admin--form-label">지점명 <span class="admin--required">*</span></label>
  10. <select
  11. v-model="formData.branch_id"
  12. class="admin--form-select"
  13. required
  14. >
  15. <option value="">지점을 선택하세요</option>
  16. <option
  17. v-for="branch in branches"
  18. :key="branch.id"
  19. :value="branch.id"
  20. :disabled="branch.is_active !== 1 && branch.is_active !== '1'"
  21. >
  22. {{ branch.name }}{{ (branch.is_active !== 1 && branch.is_active !== '1') ? ' (비활성화)' : '' }}
  23. </option>
  24. </select>
  25. </div>
  26. <!-- 아이디 -->
  27. <div class="admin--form-group">
  28. <label class="admin--form-label">아이디 <span class="admin--required">*</span></label>
  29. <input
  30. v-model="formData.user_id"
  31. type="text"
  32. class="admin--form-input"
  33. placeholder="아이디를 입력하세요"
  34. required
  35. disabled
  36. >
  37. <p class="admin--form-help">아이디는 수정할 수 없습니다.</p>
  38. </div>
  39. <!-- 비밀번호 (선택사항) -->
  40. <div class="admin--form-group">
  41. <label class="admin--form-label">비밀번호</label>
  42. <div class="admin--password-input-wrapper">
  43. <input
  44. v-model="formData.password"
  45. :type="showPassword ? 'text' : 'password'"
  46. class="admin--form-input"
  47. placeholder="변경할 비밀번호를 입력하세요"
  48. >
  49. <button
  50. type="button"
  51. class="admin--password-toggle"
  52. @click="showPassword = !showPassword"
  53. >
  54. {{ showPassword ? '👁️' : '👁️‍🗨️' }}
  55. </button>
  56. </div>
  57. <p class="admin--form-help">비밀번호를 변경하지 않으려면 비워두세요.</p>
  58. </div>
  59. <!-- 비밀번호 확인 -->
  60. <div v-if="formData.password" class="admin--form-group">
  61. <label class="admin--form-label">비밀번호 확인 <span class="admin--required">*</span></label>
  62. <div class="admin--password-input-wrapper">
  63. <input
  64. v-model="formData.password_confirm"
  65. :type="showPasswordConfirm ? 'text' : 'password'"
  66. class="admin--form-input"
  67. placeholder="비밀번호를 다시 입력하세요"
  68. required
  69. >
  70. <button
  71. type="button"
  72. class="admin--password-toggle"
  73. @click="showPasswordConfirm = !showPasswordConfirm"
  74. >
  75. {{ showPasswordConfirm ? '👁️' : '👁️‍🗨️' }}
  76. </button>
  77. </div>
  78. </div>
  79. <!-- 관리자명 -->
  80. <div class="admin--form-group">
  81. <label class="admin--form-label">관리자명 <span class="admin--required">*</span></label>
  82. <input
  83. v-model="formData.name"
  84. type="text"
  85. class="admin--form-input"
  86. placeholder="이름을 입력하세요"
  87. required
  88. >
  89. </div>
  90. <!-- 이메일 -->
  91. <div class="admin--form-group">
  92. <label class="admin--form-label">이메일 <span class="admin--required">*</span></label>
  93. <input
  94. v-model="formData.email"
  95. type="email"
  96. class="admin--form-input"
  97. placeholder="email@example.com"
  98. required
  99. >
  100. </div>
  101. <!-- 인사말 -->
  102. <div class="admin--form-group">
  103. <label class="admin--form-label">인사말</label>
  104. <textarea
  105. v-model="formData.greeting"
  106. class="admin--form-textarea"
  107. placeholder="인사말을 입력하세요 (엔터로 줄바꿈 가능)"
  108. rows="5"
  109. ></textarea>
  110. <p class="admin--form-help">영업사원 목록 페이지 상단에 표시됩니다.</p>
  111. </div>
  112. <!-- 사진 -->
  113. <div class="admin--form-group">
  114. <label class="admin--form-label">사진</label>
  115. <input
  116. type="file"
  117. accept="image/*"
  118. class="admin--form-file"
  119. @change="handlePhotoUpload"
  120. >
  121. <div v-if="photoPreview || formData.photo_url" class="admin--image-preview">
  122. <img :src="photoPreview || getImageUrl(formData.photo_url)" alt="미리보기">
  123. <button type="button" class="admin--btn-remove-image" @click="removePhoto">
  124. 삭제
  125. </button>
  126. </div>
  127. </div>
  128. <!-- 버튼 영역 -->
  129. <div class="admin--form-actions">
  130. <button
  131. type="submit"
  132. class="admin--btn admin--btn-primary"
  133. :disabled="isSaving"
  134. >
  135. {{ isSaving ? '저장 중...' : '확인' }}
  136. </button>
  137. <button
  138. type="button"
  139. class="admin--btn admin--btn-secondary"
  140. @click="goToList"
  141. >
  142. 목록
  143. </button>
  144. </div>
  145. <!-- 성공/에러 메시지 -->
  146. <div v-if="successMessage" class="admin--alert admin--alert-success">
  147. {{ successMessage }}
  148. </div>
  149. <div v-if="errorMessage" class="admin--alert admin--alert-error">
  150. {{ errorMessage }}
  151. </div>
  152. </form>
  153. </div>
  154. </template>
  155. <script setup>
  156. import { ref, onMounted } from 'vue'
  157. import { useRoute, useRouter } from 'vue-router'
  158. definePageMeta({
  159. layout: 'admin',
  160. middleware: ['auth']
  161. })
  162. const route = useRoute()
  163. const router = useRouter()
  164. const { get, put, upload } = useApi()
  165. const { getImageUrl } = useImage()
  166. const isLoading = ref(true)
  167. const isSaving = ref(false)
  168. const successMessage = ref('')
  169. const errorMessage = ref('')
  170. const showPassword = ref(false)
  171. const showPasswordConfirm = ref(false)
  172. const branches = ref([])
  173. const photoPreview = ref(null)
  174. const photoFile = ref(null)
  175. const formData = ref({
  176. branch_id: '',
  177. user_id: '',
  178. password: '',
  179. password_confirm: '',
  180. name: '',
  181. email: '',
  182. greeting: '',
  183. photo_url: ''
  184. })
  185. // 지점 목록 로드
  186. const loadBranches = async () => {
  187. const { data, error } = await get('/branch/list', { per_page: 1000 })
  188. console.log('[BranchManagerEdit] API 응답:', { data, error })
  189. if (data?.success && data?.data) {
  190. branches.value = data.data.items || []
  191. console.log('[BranchManagerEdit] 지점 목록 로드 성공')
  192. }
  193. }
  194. // 데이터 로드
  195. const loadManager = async () => {
  196. isLoading.value = true
  197. const id = route.params.id
  198. const { data, error } = await get(`/branch/manager/${id}`)
  199. console.log('[BranchManagerEdit] 데이터 로드:', { data, error })
  200. if (data?.success && data?.data) {
  201. const manager = data.data
  202. formData.value = {
  203. branch_id: manager.branch_id || '',
  204. user_id: manager.user_id || '',
  205. password: '',
  206. password_confirm: '',
  207. name: manager.name || '',
  208. email: manager.email || '',
  209. greeting: manager.greeting || '',
  210. photo_url: manager.photo_url || ''
  211. }
  212. photoPreview.value = null
  213. console.log('[BranchManagerEdit] 로드 성공')
  214. }
  215. isLoading.value = false
  216. }
  217. // 사진 업로드
  218. const handlePhotoUpload = (event) => {
  219. const file = event.target.files[0]
  220. if (!file) return
  221. if (!file.type.startsWith('image/')) {
  222. alert('이미지 파일만 업로드 가능합니다.')
  223. return
  224. }
  225. photoFile.value = file
  226. const reader = new FileReader()
  227. reader.onload = (e) => {
  228. photoPreview.value = e.target.result
  229. }
  230. reader.readAsDataURL(file)
  231. }
  232. // 사진 삭제
  233. const removePhoto = () => {
  234. photoPreview.value = null
  235. photoFile.value = null
  236. formData.value.photo_url = ''
  237. }
  238. // 폼 제출
  239. const handleSubmit = async () => {
  240. successMessage.value = ''
  241. errorMessage.value = ''
  242. // 유효성 검사
  243. if (!formData.value.branch_id) {
  244. errorMessage.value = '지점을 선택하세요.'
  245. return
  246. }
  247. if (formData.value.password && formData.value.password !== formData.value.password_confirm) {
  248. errorMessage.value = '비밀번호가 일치하지 않습니다.'
  249. return
  250. }
  251. if (!formData.value.name) {
  252. errorMessage.value = '관리자명을 입력하세요.'
  253. return
  254. }
  255. if (!formData.value.email) {
  256. errorMessage.value = '이메일을 입력하세요.'
  257. return
  258. }
  259. isSaving.value = true
  260. try {
  261. let photoUrl = formData.value.photo_url
  262. // 새 사진 업로드
  263. if (photoFile.value) {
  264. const formDataImage = new FormData()
  265. formDataImage.append('file', photoFile.value)
  266. const { data: uploadData, error: uploadError } = await upload('/upload/bmanager-image', formDataImage)
  267. console.log('[BranchManagerEdit] 이미지 업로드 응답:', { data: uploadData, error: uploadError })
  268. if (uploadError) {
  269. errorMessage.value = '사진 업로드에 실패했습니다: ' + (uploadError.message || uploadError)
  270. isSaving.value = false
  271. return
  272. }
  273. if (!uploadData?.success || !uploadData?.data?.url) {
  274. errorMessage.value = '사진 업로드 응답이 올바르지 않습니다.'
  275. isSaving.value = false
  276. return
  277. }
  278. photoUrl = uploadData.data.url
  279. console.log('[BranchManagerEdit] 업로드된 이미지 URL:', photoUrl)
  280. }
  281. const submitData = {
  282. branch_id: formData.value.branch_id,
  283. name: formData.value.name,
  284. email: formData.value.email,
  285. greeting: formData.value.greeting,
  286. photo_url: photoUrl
  287. }
  288. // 비밀번호가 입력된 경우에만 포함
  289. if (formData.value.password) {
  290. submitData.password = formData.value.password
  291. }
  292. const id = route.params.id
  293. const { data, error } = await put(`/branch/manager/${id}`, submitData)
  294. if (error) {
  295. errorMessage.value = error.message || '수정에 실패했습니다.'
  296. } else {
  297. successMessage.value = '지점장 정보가 수정되었습니다.'
  298. setTimeout(() => {
  299. router.push('/site-manager/branch/manager')
  300. }, 1000)
  301. }
  302. } catch (error) {
  303. errorMessage.value = '서버 오류가 발생했습니다.'
  304. console.error('Save error:', error)
  305. } finally {
  306. isSaving.value = false
  307. }
  308. }
  309. // 목록으로 이동
  310. const goToList = () => {
  311. router.push('/site-manager/branch/manager')
  312. }
  313. onMounted(async () => {
  314. await loadBranches()
  315. await loadManager()
  316. })
  317. </script>